/*
 * $Id: Socket.cc,v 1.29 2006-02-22 09:13:57 bacon Exp $
 */

#include <xpp/net/Socket.h>
#include <xpp/net/InetAddress.h>
#include <xp/bas/string.h>
#include <xp/bas/memory.h>
#include <xp/bas/locale.h>
#include <xp/bas/errno.h>
#include <xp/bas/assert.h>

#ifndef _WIN32
#include <netinet/in.h>
#include <net/if.h>
#endif

namespace xpp
{
	namespace net
	{

		int Socket::v6_supported = 0;

		Socket::Socket (int type)
		{

			// NOTICE:	
			//   The socket can't be created in the constructor
			//   as the implementaion is intended to support both 
			//   ipv4 and ipv6 seamlessly. The new name resolution
			//   API's don't effectively allow the creation of socket
			//   before the successful name lookup. So the lazy creation
			//   of sockets is empolyed.
	
			this->socket_type = type;
			this->sckhnd = INVALID_HANDLE;
			this->error_code = ERR_NONE;

			disableTimeout ();
		}

		Socket::~Socket ()
		{
			close ();
		}

		int Socket::startup (bool try_v6)
		{
			xp_sck_t sck;
		   
			if (::xp_sckstartup() == -1) return -1;

			if (try_v6) {
				if (::xp_sck_open (&sck,
					InetAddress::V6, Socket::DATAGRAM, 0) == XP_NULL) {
		#ifdef _WIN32
					if (XPP_NET_SOCKET_ERRNO == WSAEAFNOSUPPORT) {
		#else
					if (XPP_NET_SOCKET_ERRNO == EAFNOSUPPORT || 
					    XPP_NET_SOCKET_ERRNO == ENOSYS)  {
		#endif
						v6_supported = -1;
					}
				}
				else {
					::xp_sck_close (&sck);
					v6_supported = 1;
				}
			}
			else v6_supported = -1;

			return 0;
		}

		int Socket::cleanup ()
		{
			return ::xp_sckcleanup ();
		}

		int Socket::open (Handle handle)
		{
			XPP_NET_SOCKET_CHECK_OPEN_HANDLE(this->sckhnd);

			int tmp;
			len_t len = sizeof(tmp);

			if (xp_sck_openhnd(this, handle) != XP_NULL) return -1;

			if (xp_sck_getopt (this, 
				SOL_SOCKET, SO_TYPE, (char*)&tmp, &len) == -1) {
				this->error_code  = ERR_TYPE_RETRIEVAL;
				xp_sck_forfeit (this);
				this->sckhnd = INVALID_HANDLE;
				return -1;
			}

			if (this->socket_type != 0 && 
			    this->socket_type != tmp) {
				xp_sck_forfeit (this);
				this->error_code  = ERR_TYPE_MISMATCH;
				return -1;
			}

			this->socket_type = tmp;
			return 0;
		}

		int Socket::close ()
		{
			XPP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
		
			int n = ::xp_sck_close (this);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			else this->sckhnd = INVALID_HANDLE;

			return n;
		}

		int Socket::shutdown (int how)
		{
			XPP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);

			int n = ::xp_sck_shtdwn (this, how);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		xp_ssize_t Socket::send (
			const void* data, xp_size_t size, int flags) 
		{
			XPP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
			int n = ::xp_sck_send (this, data, size, flags);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		xp_ssize_t Socket::sendAll (const void* data, xp_size_t size, int flags) 
		{
			XPP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
			int n = ::xp_sck_sendall (this, data, size, flags);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		xp_ssize_t Socket::receive (void* buf, xp_size_t size, int flags) 
		{
			XPP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);

			int n = ::xp_sck_recv (this, buf, size, flags);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		xp_ssize_t Socket::receiveAll (void* buf, xp_size_t size, int flags) 
		{
			XPP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);

			int n = ::xp_sck_recvall (this, buf, size, flags);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::getOption (
			int level, int optname, void* optval, len_t* optlen)
		{
			XPP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
			int n = ::xp_sck_getopt (
				this, level, optname, (char*)optval, optlen);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_SOCKOPT);
			}
			return n;
		}

		int Socket::setOption (
			int level, int optname, const void* optval, len_t optlen)
		{
			XPP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
			int n = ::xp_sck_setopt (
				this, level, optname, (const char*)optval, optlen);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_SOCKOPT);
			}
			return n;
		}

		int Socket::setDebug (int n) 
		{
			return setOption (
				SOL_SOCKET, SO_DEBUG, (char*)&n, xp_sizeof(n));
		};

		int Socket::setReuseAddr (int n) 
		{
			return setOption (
				SOL_SOCKET, SO_REUSEADDR, (char*)&n, xp_sizeof(n));
		}

		int Socket::setKeepAlive (int n) 
		{
			return setOption (
				SOL_SOCKET, SO_KEEPALIVE, (char*)&n, xp_sizeof(n));
		}

		int Socket::setBroadcast (int n) 
		{
			return setOption (
				SOL_SOCKET, SO_BROADCAST, (char*)&n, xp_sizeof(n));
		}
		
		int Socket::setSendBuf (unsigned int size) 
		{
			return setOption (
				SOL_SOCKET, SO_SNDBUF, (char*)&size, xp_sizeof(size));
		}

		int Socket::setRecvBuf (unsigned int size) 
		{
			return setOption (
				SOL_SOCKET, SO_RCVBUF, (char*)&size, xp_sizeof(size));
		}
		
		int Socket::setLingerOn (int sec)
		{
			struct linger lng;
			lng.l_onoff = 1;
			lng.l_linger = sec;
			return setOption (
				SOL_SOCKET, SO_LINGER, (char*)&lng, xp_sizeof(lng));
		}

		int Socket::setLingerOff ()
		{
			struct linger lng;
			lng.l_onoff = 0;
			lng.l_linger = 0;
			return setOption (SOL_SOCKET, 
				SO_LINGER, (char*)&lng, xp_sizeof(lng));
		}
		
		int Socket::setTcpNodelay (int n) 
		{
		#ifdef TCP_NODELAY
			return setOption (IPPROTO_TCP, 
				TCP_NODELAY, (char*)&n, xp_sizeof(n));
		#else
			this->error_code = ERR_NOSUPPORT;
			return -1;
		#endif
		}

		int Socket::setOobInline (int n)
		{
		#ifdef SO_OOBINLINE
			return setOption (SOL_SOCKET, 	
				SO_OOBINLINE, (char*)&n, xp_sizeof(n));
		#else
			this->error_code = ERR_NOSUPPORT;
			return -1;
		#endif
		}

		int Socket::setIpv6Only (int n)
		{
		#ifdef IPV6_V6ONLY
			return setOption (IPPROTO_IPV6, 
				IPV6_V6ONLY, (char*)&n, xp_sizeof(n));
		#else
			this->error_code = ERR_NOSUPPORT;
			return -1;
		#endif
		}	

		int Socket::bindToDevice (const xp_char_t* device)
		{
		#ifdef SO_BINDTODEVICE
			if (device == XP_NULL) {
				return setOption (SOL_SOCKET, 
					SO_BINDTODEVICE, XP_NULL, 0);
			}
			else {
				struct ifreq iface;
				xp_memset (&iface, 0, xp_sizeof(iface));

		#ifdef XP_CHAR_IS_MCHAR
				xp_strxcpy (iface.ifr_name, xp_countof(iface.ifr_name), device);
		#else
				if (xp_wcstomcs(device, iface.ifr_name,
					xp_countof(iface.ifr_name)) < xp_strlen(device)) {
					this->error_code = ERR_INVALID;
					return -1;
				};
		#endif
				return setOption (SOL_SOCKET, 
					SO_BINDTODEVICE, (char*)&iface, xp_sizeof(iface));
			}
		#else
			this->error_code = ERR_NOSUPPORT;
			return -1;
		#endif
		}

		int Socket::unbindToDevice ()
		{
			return bindToDevice (XP_NULL);
		}

		int Socket::create_socket (int domain, int type, int proto)
		{
			xp_assert (this->sckhnd == INVALID_HANDLE);

			// domain adjustment
			if (domain == InetAddress::V0 && 
			    v6_supported == -1) domain = InetAddress::V4;

			if (::xp_sck_open(this, domain, type, proto) == XP_NULL) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_CREATE);
				return -1;
			}

			return 0;
		}

		int Socket::bind_socket (addr_t* addr, len_t len)
		{
			xp_assert (this->sckhnd != INVALID_HANDLE);

			int n = ::xp_sck_bind (this, addr, len);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::connect_socket (addr_t* addr, len_t len)
		{
			xp_assert (this->sckhnd != INVALID_HANDLE);

			xp_sck_setcancel (this, XP_SCK_CONN, NULL, NULL);
			int n = ::xp_sck_conn (this, addr, len);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::connect_socket (addr_t* addr, len_t len, xp_bool_t (*cancel) (void*))
		{
			xp_assert (this->sckhnd != INVALID_HANDLE);

			xp_sck_setcancel (this, XP_SCK_CONN, cancel, this);
			int n = ::xp_sck_conn (this, addr, len);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::listen_socket (int backlog)
		{
			xp_assert (this->sckhnd != INVALID_HANDLE);

			int n = ::xp_sck_lstn (this, backlog);
			if (n == -1) {
				this->error_code = translate_errno (
					XPP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::translate_errno (int num, int def_err)
		{
		/*#ifdef _WIN32
			if (num == WSAETIMEDOUT) return ERR_TIMEDOUT;
			if (num == WSAECANCELED) return ERR_CANCELED;
			if (num == WSAEINVAL || num == WSAENOTSOCK) 
				return ERR_INVALID;
			if (num == WSAEAFNOSUPPORT || num == WSAEPROTONOSUPPORT) 
				return ERR_NOSUPPORT;
			if (num == WSAENOBUFS || num == WSAEMFILE)
				return ERR_NORESOURCE;
			if (num == WSAEACCES) return ERR_ACCESS;
			if (num == WSAEADDRINUSE) return ERR_ADDRINUSE;
			if (num == WSAECONNREFUSED) return ERR_REFUSED;
			if (num == WSAENOTCONN) return ERR_NOTCONNECTED;
			if (num == WSAENETUNREACH) return ERR_UNREACHABLE;
		#else*/
			if (num == XP_ETIMEDOUT) return ERR_TIMEDOUT;
			if (num == XP_ECANCELED) return ERR_CANCELED;
			if (num == XP_EBADF || num == XP_EINVAL || num == XP_ENOTSOCK) 
				return ERR_INVALID;
			if (num == XP_EAFNOSUPPORT || num == XP_EPROTONOSUPPORT) 
				return ERR_NOSUPPORT;
			if (num == XP_ENOMEM || num == XP_EMFILE || num == XP_EAGAIN) 
				return ERR_NORESOURCE;
			if (num == XP_EACCES) return ERR_ACCESS;
			if (num == XP_EADDRINUSE) return ERR_ADDRINUSE;
			if (num == XP_ECONNREFUSED) return ERR_REFUSED;
			if (num == XP_ENOTCONN) return ERR_NOTCONNECTED;
			if (num == XP_ENETUNREACH) return ERR_UNREACHABLE;
		/*#endif*/

			return def_err;
		}

	}
}
